/*
 * Copyright 2012 Marsrutai.lt
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package lt.marsrutai.mobile.android.activity;

import static com.google.android.gms.maps.GoogleMap.MAP_TYPE_HYBRID;
import static com.google.android.gms.maps.GoogleMap.MAP_TYPE_NORMAL;
import static com.google.android.gms.maps.GoogleMap.MAP_TYPE_SATELLITE;
import static com.google.android.gms.maps.GoogleMap.MAP_TYPE_TERRAIN;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import lt.marsrutai.mobile.android.R;
import lt.marsrutai.mobile.android.activity.intent.BaseMapIntentHolder;
import lt.marsrutai.mobile.android.util.PreferenceUtils;
import lt.marsrutai.mobile.android.util.StringUtils;
import lt.marsrutai.mobile.android.util.UnitConversionUtils;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.pm.ConfigurationInfo;
import android.location.Location;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.ToggleButton;

import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMyLocationChangeListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.UiSettings;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;

/**
 * Base map activity. By default provides current location indicator with a
 * compass, zoom controls and an ability to switch between layers.
 * 
 * @author Justas
 */
public abstract class BaseMapActivity extends BaseActivity {

    private static final String TAG = "BaseMapActivity";
    
    private static final LatLng INITIAL_LAT_LNG = new LatLng(55.153766f, 23.90625f); // Lithuania
    private static final int INITIAL_ZOOM_LEVEL = 7;

    private static final float DEFAULT_ZOOM_LEVEL = 15;
    
    /**
     * Maps available map types to spinner labels. Is later used to resolve default map type.
     */
    @SuppressLint("UseSparseArrays")
    private static final Map<Integer, Integer> mapLayerOptions = new HashMap<Integer, Integer>();
    static {
        mapLayerOptions.put(MAP_TYPE_NORMAL, R.string.map_normal);
        mapLayerOptions.put(MAP_TYPE_HYBRID, R.string.map_hybrid);
        mapLayerOptions.put(MAP_TYPE_TERRAIN, R.string.map_terrain);
        mapLayerOptions.put(MAP_TYPE_SATELLITE, R.string.map_satellite);
    }

    private GoogleMap map;
    private UiSettings uiSettings;

    private Spinner spinner;
    
    /** a list of controls to draw on the right corner of the map */
    private List<View> topRightControls = new ArrayList<View>();

    @Override
    protected final void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (!isMapSupported()) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage(R.string.no_maps_capability).setCancelable(false).setNegativeButton(R.string.back, new OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.cancel();
                    finish();
                }
            });

            builder.create().show();
        }
        else {
            onCreateInternal(savedInstanceState);
            
            // Draw controls on the right corner of the map
            for (int i = 0; i < topRightControls.size(); i++) {
                RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                p.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
                p.setMargins(0, UnitConversionUtils.dpToPx(this, 11), UnitConversionUtils.dpToPx(this, 60 + 50 * i), 0);
                ((ViewGroup) findViewById(R.id.map_container)).addView(topRightControls.get(i), p);
            }

            setUpMapIfNeeded(savedInstanceState);
        }
    }

    @Override
    protected void prepareContext() {
        super.prepareContext();
        
        setTheme(R.style.Theme_Marsrutai_NoTitleBar);
    }

    protected void onCreateInternal(Bundle savedInstanceState) {
        setContentView(getMapLayout());
   
        int mapType = PreferenceUtils.<Integer>getPreferenceValue(this, PreferenceUtils.MAP_TYPE);
        spinner = (Spinner) findViewById(R.id.layers_spinner);
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.map_layers_array, android.R.layout.simple_spinner_item);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner.setAdapter(adapter);
        spinner.setSelection(adapter.getPosition(getString(mapLayerOptions.get(mapType))));
        spinner.setOnItemSelectedListener(new OnItemSelectedListener() {

            @Override
            public void onNothingSelected(AdapterView<?> arg0) {
            }

            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                if (!isMapReady()) {
                    return;
                }
                Log.i(TAG, "item selected at position " + position + " with string " + (String) parent.getItemAtPosition(position));
                setLayer((String) parent.getItemAtPosition(position));
            }

            private void setLayer(String layerName) {
                for (Entry<Integer, Integer> entry : mapLayerOptions.entrySet()) {
                    String label = getString(entry.getValue());
   
                    if (StringUtils.equals(label, layerName)) {
                        int mapType = entry.getKey();
                        map.setMapType(mapType);
                        PreferenceUtils.setIntegerPreferenceValue(BaseMapActivity.this, PreferenceUtils.MAP_TYPE, mapType);
                        return;
                    }
                }

                Log.i(TAG, "Error setting layer with name " + layerName);
            }
        });
        
        LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        
        FrameLayout frameLayout = new FrameLayout(this);
        View view = inflater.inflate(R.layout.button_traffic, frameLayout);
        topRightControls.add(frameLayout);
        
        ToggleButton trafficToggler = (ToggleButton) view.findViewById(R.id.traffic_toggler);
        trafficToggler.setOnCheckedChangeListener(new OnCheckedChangeListener() {
            
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                getMap().setTrafficEnabled(isChecked);
            }
        });
    }

    protected int getMapLayout() {
        return R.layout.map_layout;
    }
    
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        BaseMapIntentHolder intentHolder = new BaseMapIntentHolder(new Bundle());
        intentHolder.setCameraPosition(map.getCameraPosition());
        
        outState.putAll(intentHolder.getIntent().getExtras());
    }
    
    private void restoreMapState(Bundle savedInstanceState) {
        BaseMapIntentHolder intentHolder = new BaseMapIntentHolder(savedInstanceState);
        CameraPosition cameraPosition = intentHolder.getCameraPosition();

        if (cameraPosition != null) {
            map.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
        }
    }

    @Override
    protected final void onResume() {
        super.onResume();

        if (isMapSupported()) {
            onResumeInternal();
        }
    }

    protected void onResumeInternal() {
        setUpMapIfNeeded(null);
    }

    /**
     * Checks whether this device supports maps v2
     * 
     * @return
     */
    protected boolean isMapSupported() {
        ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
        boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000;
        
        return supportsEs2 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
    }

    private void setUpMapIfNeeded(Bundle savedInstanceState) {
        if (map == null) {
            map = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
            
            if (map != null) {
                setUpMap(map, savedInstanceState);
            }
        }
    }
    
    protected boolean isMapReady() {
        if (map == null) {
            Toast.makeText(this, R.string.map_not_ready, Toast.LENGTH_SHORT).show();
            return false;
        }
        return true;
    }

    /**
     * Manipulates the map. Is called after map verification.
     * 
     * @param map
     */
    protected void setUpMap(GoogleMap map, Bundle savedInstanceState) {
        map.setMyLocationEnabled(true);
        uiSettings = map.getUiSettings();
        uiSettings.setCompassEnabled(true);
        uiSettings.setMyLocationButtonEnabled(true);
        uiSettings.setAllGesturesEnabled(true);
        uiSettings.setZoomControlsEnabled(true);

        int mapType = PreferenceUtils.<Integer>getPreferenceValue(this, PreferenceUtils.MAP_TYPE);
        map.setMapType(mapType);
        
        if (savedInstanceState != null) {
            restoreMapState(savedInstanceState);
        }
        else {
            CameraUpdate initialPoint = CameraUpdateFactory.newLatLngZoom(INITIAL_LAT_LNG, INITIAL_ZOOM_LEVEL);
            map.moveCamera(initialPoint);
        }
    }

    /**
     * <p>
     * Centers the map to the current location.
     * </p>
     */
    protected void animateToCurrentLocation() {
        Location myLocation = getMap().getMyLocation();
        if (myLocation != null) {
            LatLng latLng = new LatLng(myLocation.getLatitude(), myLocation.getLongitude());
            zoomToItems(latLng);
        }
        else {
            getMap().setOnMyLocationChangeListener(new OnMyLocationChangeListener() {

                @Override
                public void onMyLocationChange(Location location) {
                    LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
                    zoomToItems(latLng);
                    getMap().setOnMyLocationChangeListener(null);
                }
            });
        }
    }
    
    /**
     * Convenience method for {@link #zoomToItems(LatLng...)}.
     * 
     * @param items
     * @see {@link #zoomToItems(LatLng...)}
     */
    protected void zoomToItems(Collection<LatLng> items) {
        zoomToItems(items.toArray(new LatLng[items.size()]));
    }

    /**
     * Zoom level is determined automatically in such a manner, that all the
     * provided items are visible in the screen. If there is only one item, it is centered with {@link #DEFAULT_ZOOM_LEVEL}.
     * 
     * @param items
     * @see http://stackoverflow.com/a/5242149
     */
    protected void zoomToItems(LatLng... items) {
        double minLat = Double.MAX_VALUE;
        double maxLat = Double.MIN_VALUE;
        double minLon = Double.MAX_VALUE;
        double maxLon = Double.MIN_VALUE;

        for (LatLng item : items) {

            double lat = item.latitude;
            double lon = item.longitude;

            maxLat = Math.max(lat, maxLat);
            minLat = Math.min(lat, minLat);
            maxLon = Math.max(lon, maxLon);
            minLon = Math.min(lon, minLon);
        }
        
        CameraUpdate cameraUpdate = null;
        
        if (items.length > 1) {
            cameraUpdate = CameraUpdateFactory.newLatLngBounds(new LatLngBounds(new LatLng(minLat, minLon), new LatLng(maxLat, maxLon)), UnitConversionUtils.dpToPx(this, 50));
        }
        else {
            cameraUpdate = CameraUpdateFactory.newLatLngZoom(items[0], DEFAULT_ZOOM_LEVEL);
        }
        
        animate(cameraUpdate);
    }

    /**
     * Performs camera update. If map layout is already requested, camera
     * animation is started immediately, otherwise a layout listener is
     * registered - camera update will be performed when the layout is ready.
     * 
     * @param cameraUpdate
     */
    private void animate(final CameraUpdate cameraUpdate) {
        // Center map and zoom in to the route
        final View mapView = getSupportFragmentManager().findFragmentById(R.id.map).getView();
    
        if (mapView.getViewTreeObserver().isAlive()) {
            if (!mapView.isLayoutRequested()) {
                map.animateCamera(cameraUpdate);
            }
            else {
                mapView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
                    @SuppressWarnings("deprecation")
                    @SuppressLint("NewApi")
                    @Override
                    public void onGlobalLayout() {
                        map.animateCamera(cameraUpdate);
    
                        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                            mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                        }
                        else {
                            mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        }
                    }
                });
            }
        }
    }
    
    public GoogleMap getMap() {
        return map;
    }

    public List<View> getTopRightControls() {
        return topRightControls;
    }

}
